﻿namespace StyleCop.CSharp.JslRefactoringRules
{
    using System;
    using System.Linq;

    /// <summary>
    /// Searches for variables that are assigned but the value is never used.
    /// </summary>
    public class LocalVariableAssignmentUnused : CsRuleBase<RuleAnalyzer>
    {
        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="LocalVariableAssignmentUnused"/> class.
        /// </summary>
        /// <param name="analyzer">The analyzer.</param>
        /// <param name="document">The document.</param>
        public LocalVariableAssignmentUnused(RuleAnalyzer analyzer, CsDocument document)
            : base(analyzer)
        {
            this.Document = document;
        }
        #endregion Constructors

        #region Public Methods
        /// <summary>
        /// Analyzes the graph.
        /// </summary>
        /// <param name="element">The element the graph was built from.</param>
        /// <param name="graph">The graph.</param>
        public override void AnalyzeGraph(CsElement element, CodeUnitGraph graph)
        {
            if (this.Analyzer.IsRuleEnabled(this.Document, RuleAnalyzer.LocalVariableAssignmentUnused))
            {
                this.CheckForUnusedAssignments(element, graph);
            }
        }
        #endregion Public Methods

        #region Private Methods
        private void CheckForUnusedAssignments(CsElement element, CodeUnitGraph graph)
        {
            // Get all the variable usages in the graph so we don't have to call GetVariableUsage later.
            // Key = the Literal Expression that is a variable.
            // Value = the variable usage.
            var allVariableUsages =
                (from literalExpression in graph.Vertices.OfType<LiteralExpression>()
                 let variableUsage = literalExpression.GetVariableUsage()
                 where variableUsage != null
                 select variableUsage)
                 .ToDictionary(variableUsage => variableUsage.Source);

            var allVariablesBeingSet =
                from variableUsage in allVariableUsages.Values
                where variableUsage.Usage.IsVariableSetting()
                select variableUsage;

            foreach (VariableUsageInformation variableUsageToInvestigate in allVariablesBeingSet)
            {
                bool variableWasHoisted = false;
                GraphSearchState result = CodeUnitGraphHelper.SearchChildCodePaths(
                    graph,
                    variableUsageToInvestigate.Source,
                    codeUnit =>
                    {
                        LiteralExpression literalExpression = codeUnit as LiteralExpression;
                        if (literalExpression != null)
                        {
                            VariableUsageInformation thisVariableUsage;
                            if (allVariableUsages.TryGetValue(literalExpression, out thisVariableUsage))
                            {
                                // Make sure we are looking at the same variable.
                                if (object.ReferenceEquals(variableUsageToInvestigate.Variable, thisVariableUsage.Variable))
                                {
                                    // If the two usages are hoisted inside different code units then we can't determine anything.
                                    if (!object.ReferenceEquals(variableUsageToInvestigate.HoistedInside, thisVariableUsage.HoistedInside))
                                    {
                                        variableWasHoisted = true;
                                        return GraphSearchState.StopPath;
                                    }

                                    switch (thisVariableUsage.Usage)
                                    {
                                        case VariableUsage.Access:
                                        case VariableUsage.AccessAndAssignment:
                                        case VariableUsage.RefParameter:
                                            // If this is a usage than we've found that the variable was accessed first.
                                            return GraphSearchState.Found;

                                        case VariableUsage.Assignment:
                                        case VariableUsage.Declaration:
                                        case VariableUsage.DeclarationAndAssignment:
                                        case VariableUsage.OutParameter:
                                            // If this is the variable being set then we don't need to continue down this path.
                                            // We do have to keep looking at other paths though.
                                            return GraphSearchState.StopPath;

                                        default:
                                            throw new InvalidOperationException("Unknown variable used type.");
                                    }
                                }
                            }
                        }

                        return GraphSearchState.ContinuePath;
                    });

                if (!variableWasHoisted
                    && (result != GraphSearchState.Found)
                    && (variableUsageToInvestigate.Usage != VariableUsage.OutParameter)
                    && (variableUsageToInvestigate.Usage != VariableUsage.RefParameter)
                    && !element.IsOutOrRefParameter(variableUsageToInvestigate.Variable))
                {
                    this.AddViolation(
                        variableUsageToInvestigate.Source.ParentElement(),
                        variableUsageToInvestigate.Source.LineNumber,
                        RuleAnalyzer.LocalVariableAssignmentUnused,
                        variableUsageToInvestigate.Variable.Name);
                }
            }
        }
        #endregion Private Methods
    }
}